Skip to content

fix(http): route error and empty-result messages to stderr#485

Closed
abhiram304 wants to merge 1 commit intogoogleworkspace:mainfrom
abhiram304:fix/stderr-audit
Closed

fix(http): route error and empty-result messages to stderr#485
abhiram304 wants to merge 1 commit intogoogleworkspace:mainfrom
abhiram304:fix/stderr-audit

Conversation

@abhiram304
Copy link
Contributor

Summary

  • gmail/triage.rs was printing "No messages found matching query: {query}" via println! on both the null-messages and empty-array paths — this corrupted stdout for gws gmail +triage | jq style pipelines. Changed to eprintln!.
  • modelarmor.rs (model_armor_post) was printing the raw API response body unconditionally before the status check, so on HTTP 4xx/5xx the error body leaked to stdout. Moved println!("{text}") inside the success branch and included {text} in the Err message so callers get actionable diagnostics without stdout pollution.

Test plan

  • gws gmail +triage --query label:inbox 2>/dev/null | jq . — stdout is valid JSON only, empty-result message appears on stderr
  • Simulate a Model Armor 403: error message should include both status code and response body; stdout must remain empty
  • cargo test — all tests pass (triage empty-result message test, modelarmor error path test)

🤖 Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Mar 14, 2026

🦋 Changeset detected

Latest commit: d3b32d3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@googleworkspace-bot googleworkspace-bot added the area: core Core CLI parsing, commands, error handling, utilities label Mar 14, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the application's output behavior by ensuring that human-readable messages and API error details are directed to stderr, while only valid, machine-readable data is sent to stdout. This change significantly improves compatibility with command-line tools and enhances diagnostic clarity by providing more comprehensive error information without polluting expected data streams.

Highlights

  • Standard Stream Correction for Gmail Triage: Corrected the gmail/triage.rs module to output 'No messages found' messages to stderr instead of stdout. This prevents corruption of stdout for command-line pipelines that expect valid JSON, such as those using jq.
  • Improved Error Handling for Model Armor API: Modified modelarmor.rs to prevent raw API error responses from leaking to stdout. The println! for the response body was moved to only execute on successful API calls, and non-success HTTP responses now include the full response body within the Err message for better diagnostics.
  • New Test Cases: Added new unit tests to gmail/triage.rs and modelarmor.rs to explicitly verify that empty-result messages are not valid JSON (and thus belong on stderr) and that API error messages correctly include both status codes and response bodies.
Changelog
  • .changeset/stderr-audit.md
    • Added a new changeset file documenting the fix for stdout/stderr contract.
  • src/helpers/gmail/triage.rs
    • Changed 'No messages found' output from println! to eprintln! to direct it to standard error.
    • Introduced a new test case to confirm that the empty-result message is not valid JSON.
  • src/helpers/modelarmor.rs
    • Relocated the println! statement for API response bodies to only execute on successful HTTP statuses.
    • Updated error handling to include the full response body in the GwsError message for non-success HTTP statuses.
    • Added a new test to verify that error messages contain both the HTTP status and the response body.
Activity
  • No human activity has been recorded on this pull request since its creation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly routes human-readable messages and HTTP error bodies to stderr, which is a great improvement for consumers of the CLI that pipe stdout to other tools like jq. The changes in gmail/triage.rs and modelarmor.rs are logical and well-implemented. I've added a couple of comments regarding the newly added tests, with suggestions to make them more robust and less prone to giving a false sense of security.

Two violations were breaking pipe-friendly use of the CLI:

1. gmail/triage.rs printed "No messages found…" via println! on both
   the null-messages and empty-array paths, corrupting stdout for
   `gws gmail +triage | jq` pipelines. Changed to eprintln!.

2. modelarmor.rs printed the raw API response body unconditionally
   (before the status check), so on HTTP error the error body leaked
   to stdout instead of surfacing in the error return. Moved the
   println! inside the success branch and included the body in the
   Err message for better diagnostics.
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly routes error and informational messages to stderr instead of stdout, which is a great improvement for pipeline-based workflows. The changes in gmail/triage.rs and modelarmor.rs are well-targeted to fix this issue. I've added a comment with a suggestion to improve testing robustness, pointing out that a new test in modelarmor.rs doesn't fully test the intended error path, which is crucial for ensuring correct error message routing.

@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 77.77778% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.59%. Comparing base (1308786) to head (d3b32d3).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/helpers/gmail/triage.rs 77.77% 2 Missing ⚠️
src/helpers/modelarmor.rs 77.77% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #485      +/-   ##
==========================================
+ Coverage   67.57%   67.59%   +0.02%     
==========================================
  Files          40       40              
  Lines       17475    17489      +14     
==========================================
+ Hits        11808    11822      +14     
  Misses       5667     5667              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jpoehnelt pushed a commit that referenced this pull request Mar 17, 2026
…bels, auth propagation

Component 1 (PR #485): Route triage 'no messages' and modelarmor error
bodies to stderr so stdout stays machine-readable.

Component 2 (PR #466): Add colored error[variant]: labels to stderr
on TTY, respecting NO_COLOR. Replace emoji hint with colorized text.

Component 3 (PR #446): Propagate auth errors as GwsError::Auth in
calendar, chat, docs, drive, script, sheets helpers instead of
silently proceeding unauthenticated. dry-run bypass preserved.
jpoehnelt added a commit that referenced this pull request Mar 17, 2026
* fix: stderr/output hygiene rollup — diagnostics to stderr, colored labels, auth propagation

Component 1 (PR #485): Route triage 'no messages' and modelarmor error
bodies to stderr so stdout stays machine-readable.

Component 2 (PR #466): Add colored error[variant]: labels to stderr
on TTY, respecting NO_COLOR. Replace emoji hint with colorized text.

Component 3 (PR #446): Propagate auth errors as GwsError::Auth in
calendar, chat, docs, drive, script, sheets helpers instead of
silently proceeding unauthenticated. dry-run bypass preserved.

* fix: deduplicate accessNotConfigured stderr output

Use if/else so that accessNotConfigured errors get the specialized
hint guidance instead of redundantly printing both the generic summary
and the hint. Non-accessNotConfigured Api errors and all other variants
still get the generic error[variant]: summary line.

* test: remove misleading model_armor_post error format test

model_armor_post function. A proper integration test would require
HTTP mocking (e.g. mockito/wiremock) which is out of scope for this PR.

* refactor: deduplicate error printing else branches

Use early return in accessNotConfigured branch so the generic
eprintln! only appears once, eliminating the duplicated else blocks.

* security: sanitize error messages before printing to stderr

Add sanitize_for_terminal() to strip control characters (ANSI escape
sequences, bell, backspace, etc.) from error messages before printing
to stderr, preventing terminal escape injection from API responses.
Newlines and tabs are preserved for readability.

The function is pub(crate) so it can be reused by other modules that
print untrusted content to stderr.

* fix: sanitize all stderr error output across codebase

Apply sanitize_for_terminal() to all 16 remaining eprintln sites
that print unsanitized error strings to stderr. This prevents
terminal escape sequence injection through error messages.

Files updated:
- workflows.rs (4 sites)
- watch.rs (2 sites)
- gmail/mod.rs (3 sites)
- executor.rs (1 site)
- subscribe.rs (1 site)
- token_storage.rs (2 sites)
- credential_store.rs (2 sites)
- setup.rs (1 site)
- generate_skills.rs (1 site)

Also fixes clippy: map_err -> inspect_err where closure only logs.

---------

Co-authored-by: jpoehnelt-bot <jpoehnelt-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Core CLI parsing, commands, error handling, utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants